/*
 * Copyright (c) 2017-present, CV4J Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cv4j.core.pixels;

import com.cv4j.core.datamodel.ByteProcessor;
import com.cv4j.core.datamodel.ColorProcessor;
import com.cv4j.core.datamodel.ImageProcessor;
import com.cv4j.core.datamodel.Rect;
import com.cv4j.exception.CV4JException;
import com.cv4j.image.util.Preconditions;
import com.cv4j.image.util.Tools;

import java.util.Optional;

/**
 * 操作符
 *
 * @author dev
 * @date 2021/07/26
 */
public final class Operator {
    /**
     * 添加
     *
     * @param image1 image1
     * @param image2 image2
     * @return {@link ImageProcessor}
     */
    public static Optional<ImageProcessor> add(ImageProcessor image1, ImageProcessor image2) {
        if (!checkParams(image1, image2)) {
            return Optional.empty();
        }
        int channels = image1.getChannels();
        int w1 = image1.getWidth();
        int h1 = image1.getHeight();
        ImageProcessor dst = (channels == 3) ? new ColorProcessor(w1, h1) : new ByteProcessor(w1, h1);
        int size = w1 * h1;
        int a1 = 0;
        int b1 = 0;
        int c1 = 0;
        for (int i1 = 0; i1 < size; i1++) {
            for (int n1 = 0; n1 < channels; n1++) {
                a1 = image1.toByte(n1)[i1] & 0xff;
                b1 = image2.toByte(n1)[i1] & 0xff;
                c1 = Tools.clamp(a1 + b1);
                dst.toByte(n1)[i1] = (byte) c1;
            }
        }
        return Optional.ofNullable(dst);
    }

    /**
     * 减
     *
     * @param image1 image1
     * @param image2 image2
     * @return {@link ImageProcessor}
     */
    public static Optional<ImageProcessor> substract(ImageProcessor image1, ImageProcessor image2) {
        if (!checkParams(image1, image2)) {
            return Optional.empty();
        }
        int channels = image1.getChannels();
        int w1 = image1.getWidth();
        int h1 = image1.getHeight();
        ImageProcessor dst = (channels == 3) ? new ColorProcessor(w1, h1) : new ByteProcessor(w1, h1);
        int size = w1 * h1;
        int a1 = 0;
        int b1 = 0;
        int c1 = 0;
        for (int i = 0; i < size; i++) {
            for (int n = 0; n < channels; n++) {
                a1 = image1.toByte(n)[i] & 0xff;
                b1 = image2.toByte(n)[i] & 0xff;
                c1 = Tools.clamp(a1 - b1);
                dst.toByte(n)[i] = (byte) c1;
            }
        }
        return Optional.ofNullable(dst);
    }

    /**
     * 多个
     *
     * @param image1 image1
     * @param image2 image2
     * @return {@link ImageProcessor}
     */
    public static Optional<ImageProcessor> multiple(ImageProcessor image1, ImageProcessor image2) {
        if (!checkParams(image1, image2)) {
            return Optional.empty();
        }
        int channels = image1.getChannels();
        int w1 = image1.getWidth();
        int h1 = image1.getHeight();
        ImageProcessor dst = (channels == 3) ? new ColorProcessor(w1, h1) : new ByteProcessor(w1, h1);
        int size = w1 * h1;
        int a1 = 0;
        int b1 = 0;
        int c1 = 0;
        for (int i = 0; i < size; i++) {
            for (int n = 0; n < channels; n++) {
                a1 = image1.toByte(n)[i] & 0xff;
                b1 = image2.toByte(n)[i] & 0xff;
                c1 = Tools.clamp(a1 * b1);
                dst.toByte(n)[i] = (byte) c1;
            }
        }
        return Optional.ofNullable(dst);
    }

    /**
     * 部门
     *
     * @param image1 image1
     * @param image2 image2
     * @return {@link ImageProcessor}
     */
    public static Optional<ImageProcessor> division(ImageProcessor image1, ImageProcessor image2) {
        if (!checkParams(image1, image2)) {
            return Optional.empty();
        }
        int channels = image1.getChannels();
        int w1 = image1.getWidth();
        int h1 = image1.getHeight();
        ImageProcessor dst = (channels == 3) ? new ColorProcessor(w1, h1) : new ByteProcessor(w1, h1);
        int size = w1 * h1;
        int a1 = 0;
        int b1 = 0;
        int c1 = 0;
        for (int i = 0; i < size; i++) {
            for (int n = 0; n < channels; n++) {
                a1 = image1.toByte(n)[i] & 0xff;
                b1 = image2.toByte(n)[i] & 0xff;
                c1 = b1 == 0 ? 0 : Tools.clamp(a1 / b1);
                dst.toByte(n)[i] = (byte) c1;
            }
        }
        return Optional.ofNullable(dst);
    }

    /**
     * 位和
     *
     * @param image1 image1
     * @param image2 image2
     * @return {@link ImageProcessor}
     */
    public static Optional<ImageProcessor> bitwiseAnd(ImageProcessor image1, ImageProcessor image2) {
        if (!checkParams(image1, image2)) {
            return Optional.empty();
        }
        int channels = image1.getChannels();
        int w1 = image1.getWidth();
        int h1 = image1.getHeight();
        ImageProcessor dst = (channels == 3) ? new ColorProcessor(w1, h1) : new ByteProcessor(w1, h1);
        int size = w1 * h1;
        int a1 = 0;
        int b1 = 0;
        int c1 = 0;
        for (int i = 0; i < size; i++) {
            for (int n = 0; n < channels; n++) {
                a1 = image1.toByte(n)[i] & 0xff;
                b1 = image2.toByte(n)[i] & 0xff;
                c1 = a1 & b1;
                dst.toByte(n)[i] = (byte) Tools.clamp(c1);
            }
        }
        return Optional.ofNullable(dst);
    }

    /**
     * 按位或
     *
     * @param image1 image1
     * @param image2 image2
     * @return {@link ImageProcessor}
     */
    public static Optional<ImageProcessor> bitwiseOr(ImageProcessor image1, ImageProcessor image2) {
        if (!checkParams(image1, image2)) {
            return Optional.empty();
        }
        int channels = image1.getChannels();
        int w1 = image1.getWidth();
        int h1 = image1.getHeight();
        ImageProcessor dst = (channels == 3) ? new ColorProcessor(w1, h1) : new ByteProcessor(w1, h1);
        int size = w1 * h1;
        int a1 = 0;
        int b1 = 0;
        int c1 = 0;
        for (int i = 0; i < size; i++) {
            for (int n = 0; n < channels; n++) {
                a1 = image1.toByte(n)[i] & 0xff;
                b1 = image2.toByte(n)[i] & 0xff;
                c1 = a1 | b1;
                dst.toByte(n)[i] = (byte) Tools.clamp(c1);
            }
        }
        return Optional.ofNullable(dst);
    }

    /**
     * 位不
     *
     * @param image 图像
     * @return {@link ImageProcessor}
     */
    public static ImageProcessor bitwiseNot(ImageProcessor image) {
        int channels = image.getChannels();
        int w1 = image.getWidth();
        int h1 = image.getHeight();
        ImageProcessor dst = (channels == 3) ? new ColorProcessor(w1, h1) : new ByteProcessor(w1, h1);
        int size = w1 * h1;
        int c1 = 0;
        for (int i = 0; i < size; i++) {
            for (int n = 0; n < channels; n++) {
                c1 = ~image.toByte(n)[i];
                dst.toByte(n)[i] = (byte) c1;
            }
        }
        return dst;
    }

    /**
     * 按位异或
     *
     * @param image1 image1
     * @param image2 image2
     * @return {@link ImageProcessor}
     */
    public static Optional<ImageProcessor> bitwiseXor(ImageProcessor image1, ImageProcessor image2) {
        if (!checkParams(image1, image2)) {
            return Optional.empty();
        }
        int channels = image1.getChannels();
        int w1 = image1.getWidth();
        int h1 = image1.getHeight();
        ImageProcessor dst = (channels == 3) ? new ColorProcessor(w1, h1) : new ByteProcessor(w1, h1);
        int size = w1 * h1;
        int a1 = 0;
        int b1 = 0;
        int c1 = 0;
        for (int i = 0; i < size; i++) {
            for (int n = 0; n < channels; n++) {
                a1 = image1.toByte(n)[i] & 0xff;
                b1 = image2.toByte(n)[i] & 0xff;
                c1 = a1 ^ b1;
                dst.toByte(n)[i] = (byte) Tools.clamp(c1);
            }
        }
        return Optional.ofNullable(dst);
    }

    /**
     * 增加重量
     *
     * @param image1 image1
     * @param w1 w1
     * @param image2 image2
     * @param w2 w2
     * @param gamma γ
     * @return {@link ImageProcessor}
     */
    public static Optional<ImageProcessor> addWeight(
        ImageProcessor image1, float w1, ImageProcessor image2, float w2, int gamma) {
        if (!checkParams(image1, image2)) {
            return Optional.empty();
        }
        int channels = image1.getChannels();
        int wid = image1.getWidth();
        int h1 = image1.getHeight();
        ImageProcessor dst = (channels == 3) ? new ColorProcessor(wid, h1) : new ByteProcessor(wid, h1);
        int size = wid * h1;
        int a1 = 0;
        int b1 = 0;
        int c1 = 0;
        for (int i = 0; i < size; i++) {
            for (int n = 0; n < channels; n++) {
                a1 = image1.toByte(n)[i] & 0xff;
                b1 = image2.toByte(n)[i] & 0xff;
                c1 = (int) (a1 * w1 + b1 * w2 + gamma);
                dst.toByte(n)[i] = (byte) Tools.clamp(c1);
            }
        }
        return Optional.ofNullable(dst);
    }

    /**
     * 子图像
     * ROI sub image by rect.x, rect.y, rect.width, rect.height
     *
     * @param image image
     * @param rect rect
     * @return ImageProcessor-
     * @throws CV4JException cv4 jexception
     */
    public static ImageProcessor subImage(ImageProcessor image, Rect rect) throws CV4JException {
        int channels = image.getChannels();
        int w1 = rect.width;
        int h1 = rect.height;
        ImageProcessor dst = (channels == 3) ? new ColorProcessor(w1, h1) : new ByteProcessor(w1, h1);
        int a1 = 0;
        int index = 0;
        try {
            for (int n = 0; n < channels; n++) {
                for (int row = rect.y1; row < (rect.y1 + rect.height); row++) {
                    for (int col = rect.x1; col < (rect.x1 + rect.width); col++) {
                        index = row * image.getWidth() + col;
                        a1 = image.toByte(n)[index] & 0xff;
                        index = (row - rect.y1) * w1 + (col - rect.x1);
                        dst.toByte(n)[index] = (byte) a1;
                    }
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new CV4JException("数组越界了");
        }
        return dst;
    }

    /**
     * 检查参数
     *
     * @param src1 src1
     * @param src2 src2
     * @return boolean
     */
    private static boolean checkParams(ImageProcessor src1, ImageProcessor src2) {
        return Preconditions.isNotBlank(src1)
            && Preconditions.isNotBlank(src2)
            && src1.getChannels() == src2.getChannels()
            && src1.getWidth() == src2.getWidth()
            && src1.getHeight() == src2.getHeight();
    }
}
